package com.eduagent.xwqeduagent.api.SpeechEvaluation;

import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.security.NoSuchAlgorithmException;

/**
 * 讯飞语音评测WebSocket客户端
 */
@Slf4j
public class SpeechEvaluationClient {

    private static final String API_URL = "wss://ise-api.xfyun.cn/v2/open-ise";
    private static final int FRAME_SIZE = 640; // 每一帧音频大小（字节）降为原来的一半
    private static final int FRAME_INTERVAL = 20; // 每帧发送间隔（毫秒）降为原来的一半
    
    private final String appId;
    private final String apiKey;
    private final String apiSecret;
    
    private WebSocket webSocket;
    private SpeechEvaluationListener listener;
    private OkHttpClient okHttpClient;
    
    /**
     * 构造方法
     * 
     * @param appId 讯飞应用ID
     * @param apiKey 讯飞API Key
     * @param apiSecret 讯飞API Secret
     */
    public SpeechEvaluationClient(String appId, String apiKey, String apiSecret) {
        this.appId = appId;
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
        
        // 创建OkHttpClient，设置超时时间
        this.okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .writeTimeout(15, TimeUnit.SECONDS)
                .build();
    }
    
    /**
     * 连接WebSocket服务
     * 
     * @param params 评测参数
     * @param listener 结果监听器
     * @throws Exception 连接异常
     */
    public void connect(SpeechEvaluationParams params, SpeechEvaluationListener listener) throws Exception {
        this.listener = listener;
        
        try {
            // 生成鉴权URL
            String authUrl = getAuthUrl(API_URL, apiKey, apiSecret);
            
            // 创建WebSocket连接请求
            Request request = new Request.Builder()
                    .url(authUrl)
                    .build();
            
            // 建立WebSocket连接
            webSocket = okHttpClient.newWebSocket(request, new WebSocketListener() {
                @Override
                public void onOpen(WebSocket webSocket, Response response) {
                    log.info("WebSocket连接已建立");
                    
                    try {
                        // 连接成功后发送初始数据
                        sendParams(params);
                        
                        // 通知监听器连接成功
                        if (listener != null) {
                            listener.onConnected();
                        }
                    } catch (Exception e) {
                        log.error("发送初始数据失败", e);
                        if (listener != null) {
                            listener.onError(e);
                        }
                    }
                }
                
                @Override
                public void onMessage(WebSocket webSocket, String text) {
                    log.info("收到消息: {}", text);
                    
                    try {
                        JSONObject jsonObject = new JSONObject(text);
                        int code = jsonObject.getInt("code", -1);
                        
                        if (code == 0) {
                            // 处理正常响应
                            JSONObject data = jsonObject.getJSONObject("data");
                            int status = data.getInt("status");
                            
                            // status=2表示所有结果已返回，此时data.data是最终结果
                            if (status == 2) {
                                String base64Result = data.getStr("data");
                                byte[] decodedBytes = Base64.getDecoder().decode(base64Result);
                                String xmlResult = new String(decodedBytes, StandardCharsets.UTF_8);
                                
                                if (listener != null) {
                                    listener.onResult(xmlResult);
                                }
                            }
                        } else {
                            // 处理错误响应
                            String message = jsonObject.getStr("message", "未知错误");
                            log.error("评测错误: code={}, message={}", code, message);
                            
                            if (listener != null) {
                                listener.onError(new RuntimeException("评测错误: " + code + ", " + message));
                            }
                        }
                    } catch (Exception e) {
                        log.error("处理响应失败", e);
                        if (listener != null) {
                            listener.onError(e);
                        }
                    }
                }
                
                @Override
                public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                    log.error("WebSocket连接失败", t);
                    
                    if (listener != null) {
                        listener.onError(t);
                    }
                }
                
                @Override
                public void onClosed(WebSocket webSocket, int code, String reason) {
                    log.info("WebSocket连接已关闭: code={}, reason={}", code, reason);
                    
                    if (listener != null) {
                        listener.onClosed();
                    }
                }
            });
        } catch (Exception e) {
            log.error("创建WebSocket连接失败", e);
            throw e;
        }
    }
    
    /**
     * 关闭WebSocket连接
     */
    public void close() {
        if (webSocket != null) {
            webSocket.close(1000, "主动关闭");
            webSocket = null;
        }
    }
    
    /**
     * 发送音频数据
     * 
     * @param audioData 音频字节数组
     * @throws IOException IO异常
     */
    public void sendAudio(byte[] audioData) throws IOException {
        if (webSocket == null) {
            throw new IOException("WebSocket连接尚未建立");
        }
        
        if (audioData == null || audioData.length == 0) {
            throw new IOException("音频数据为空");
        }
        
        // 添加适当的延迟，确保参数已经被处理
        try {
            Thread.sleep(500); // 500毫秒延迟，让服务器有时间处理参数
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("音频发送前的延迟等待被中断", e);
        }
        
        // 检查音频数据大小
        if (audioData.length > 5 * 1024 * 1024) {
            log.warn("音频数据过大: {} 字节，可能需要拆分处理", audioData.length);
        }
        
        // 计算总帧数和每帧大小
        final int frameSize = FRAME_SIZE;
        final int frameInterval = FRAME_INTERVAL; // ms
        final int totalFrames = (int) Math.ceil(audioData.length / (double) frameSize);
        
        log.info("开始发送音频数据，总大小: {} 字节", audioData.length);
        log.info("将分为 {} 帧发送，每帧 {} 字节，间隔 {} 毫秒", totalFrames, frameSize, frameInterval);
        
        try {
            // 逐帧发送数据
            for (int i = 0; i < totalFrames; i++) {
                // 计算当前帧的数据范围
                int start = i * frameSize;
                int end = Math.min((i + 1) * frameSize, audioData.length);
                int currentFrameSize = end - start;
                
                // 提取当前帧的数据
                byte[] frameData = new byte[currentFrameSize];
                System.arraycopy(audioData, start, frameData, 0, currentFrameSize);
                
                // 确定音频状态
                int aus;
                if (i == 0) {
                    aus = 1; // 第一帧
                } else if (i == totalFrames - 1) {
                    aus = 4; // 最后一帧
                } else {
                    aus = 2; // 中间帧
                }
                
                // 发送当前帧
                sendAudioFrame(frameData, aus);
                
                // 最后一帧不需要延时
                if (i < totalFrames - 1) {
                    try {
                        // 延时，模拟实时语音
                        Thread.sleep(frameInterval);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        throw new IOException("音频数据发送被中断", e);
                    }
                }
            }
            
            log.info("音频数据发送完成");
        } catch (Exception e) {
            if (e instanceof IOException) {
                throw (IOException) e;
            } else {
                throw new IOException("发送音频数据异常: " + e.getMessage(), e);
            }
        }
    }
    
    /**
     * 从文件发送音频数据
     * 
     * @param audioFilePath 音频文件路径
     * @throws IOException IO异常
     */
    public void sendAudioFile(String audioFilePath) throws IOException {
        byte[] audioData = Files.readAllBytes(Paths.get(audioFilePath));
        sendAudio(audioData);
    }
    
    /**
     * 发送参数数据
     * 
     * @param params 评测参数
     * @throws IOException IO异常
     */
    private void sendParams(SpeechEvaluationParams params) throws IOException {
        JSONObject json = new JSONObject();
        
        // 添加common参数
        JSONObject common = new JSONObject();
        common.set("app_id", appId);
        json.set("common", common);
        
        // 添加business参数
        JSONObject business = new JSONObject();
        business.set("sub", params.getSub());
        business.set("ent", params.getEnt());
        business.set("category", params.getCategory());
        
        // 确保文本有UTF8 BOM头
        // 根据讯飞文档，text参数需要添加UTF8 BOM头 '\uFEFF'
        String textWithBom = params.getText();
        if (textWithBom != null && !textWithBom.startsWith("\uFEFF")) {
            textWithBom = "\uFEFF" + textWithBom;
            log.debug("已为评测文本添加UTF8 BOM头");
        }
        
        // 确保文本正确编码，特别是对英文评测
        if ("en_vip".equals(params.getEnt())) {
            // 英文评测时，确保文本内容是纯英文字符
            String pureText = textWithBom.replace("\uFEFF", "");
            // 对英文文本保留更多标点符号，避免过滤掉重要字符
            pureText = pureText.replaceAll("[^a-zA-Z0-9\\s.,?!';:\\-]", "");
            textWithBom = "\uFEFF" + pureText;
            log.debug("处理后的英文评测文本: {}", pureText);
        }
        
        business.set("text", textWithBom);
        business.set("tte", "utf-8"); // 明确指定UTF-8编码
        business.set("ttp_skip", true); // 明确设置为true
        business.set("cmd", "ssb"); // 参数上传阶段
        
        // 根据语言类型设置参数
        if ("en_vip".equals(params.getEnt())) {
            // 英文评测参数
            business.set("rstcd", "utf8");
            business.set("rst", "entirety");
            business.set("ise_unite", "1");
            business.set("extra_ability", "multi_dimension");
        } else {
            // 中文评测参数
            if (params.getRstcd() != null) {
                business.set("rstcd", params.getRstcd());
            } else {
                business.set("rstcd", "utf8");
            }
        }
        
        // 添加音频格式参数
        if (params.getAue() != null) {
            business.set("aue", params.getAue());
        } else {
            // 默认为raw格式，更可靠
            business.set("aue", "raw");
        }
        
        if (params.getAuf() != null) {
            business.set("auf", params.getAuf());
        } else {
            business.set("auf", "audio/L16;rate=16000");
        }
        
        // 添加扩展参数
        if (params.getRst() != null) {
            business.set("rst", params.getRst());
        }
        if (params.getIseUnite() != null) {
            business.set("ise_unite", params.getIseUnite());
        }
        if (params.getPlev() != null) {
            business.set("plev", params.getPlev());
        } else {
            business.set("plev", "0");
        }
        if (params.getExtraAbility() != null) {
            business.set("extra_ability", params.getExtraAbility());
        }
        if (params.getGroup() != null) {
            business.set("group", params.getGroup());
        }
        if (params.getCheckType() != null) {
            business.set("check_type", params.getCheckType());
        }
        if (params.getGrade() != null) {
            business.set("grade", params.getGrade());
        }
        
        json.set("business", business);
        
        // 添加data参数
        JSONObject data = new JSONObject();
        data.set("status", 0); // 初始状态
        json.set("data", data);
        
        log.info("发送参数数据: {}", json.toString());
        boolean sent = webSocket.send(json.toString());
        if (!sent) {
            throw new IOException("发送参数数据失败");
        }
    }
    
    /**
     * 发送单帧音频数据
     * 
     * @param frameData 帧数据
     * @param aus 音频状态（1：第一帧，2：中间帧，4：最后一帧）
     * @throws IOException IO异常
     */
    private void sendAudioFrame(byte[] frameData, int aus) throws IOException {
        if (frameData.length > 19200) {
            log.warn("音频帧数据过大: {} 字节，超过19200字节可能导致10163错误，正在自动拆分", frameData.length);
            // 拆分为多个小于19200字节的帧发送
            int offset = 0;
            while (offset < frameData.length) {
                int chunkSize = Math.min(19000, frameData.length - offset);
                byte[] chunk = new byte[chunkSize];
                System.arraycopy(frameData, offset, chunk, 0, chunkSize);
                
                // 判断当前帧状态
                int currentAus;
                if (offset == 0) {
                    // 第一块，保持原始帧状态（如果是第一帧）
                    currentAus = (aus == 1) ? 1 : 2;
                } else if (offset + chunkSize >= frameData.length) {
                    // 最后一块，如果原始是最后帧则设为4
                    currentAus = (aus == 4) ? 4 : 2;
                } else {
                    // 中间块，始终为2
                    currentAus = 2;
                }
                
                // 发送当前块
                sendAudioFrameChunk(chunk, currentAus);
                
                offset += chunkSize;
            }
            return;
        }
        
        sendAudioFrameChunk(frameData, aus);
    }
    
    /**
     * 发送单个音频帧块
     * 
     * @param frameData 帧数据
     * @param aus 音频状态
     * @throws IOException IO异常
     */
    private void sendAudioFrameChunk(byte[] frameData, int aus) throws IOException {
        JSONObject json = new JSONObject();
        
        // 添加business参数
        JSONObject business = new JSONObject();
        business.set("cmd", "auw"); // 音频上传阶段
        business.set("aus", aus);   // 音频状态: 1-第一帧, 2-中间帧, 4-最后一帧
        json.set("business", business);
        
        // 添加data参数
        JSONObject data = new JSONObject();
        
        // 根据文档，音频状态对应data中的status:
        // aus=1 (第一帧) 或 aus=2 (中间帧) 对应 status=1
        // aus=4 (最后一帧) 对应 status=2
        int status = (aus == 4) ? 2 : 1;
        data.set("status", status);
        
        // Base64编码音频数据
        String base64Data = Base64.getEncoder().encodeToString(frameData);
        if (base64Data.length() > 26000) {
            log.warn("Base64编码后音频帧数据过大: {} 字节，超过26000字节可能导致10163错误", base64Data.length());
            throw new IOException("音频帧数据太大，Base64编码后超过26000字节限制");
        }
        
        data.set("data", base64Data);
        json.set("data", data);
        
        // 发送数据
        String message = json.toString();
        
        // 增加重试机制
        int maxRetries = 3;
        boolean sent = false;
        
        for (int i = 0; i < maxRetries && !sent; i++) {
            try {
                sent = webSocket.send(message);
                if (!sent && i < maxRetries - 1) {
                    log.warn("发送音频数据帧失败，正在重试 ({}/{})", i + 1, maxRetries);
                    Thread.sleep(100); // 重试前短暂延时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IOException("发送音频数据帧被中断", e);
            }
        }
        
        if (!sent) {
            throw new IOException("发送音频数据帧失败");
        }
    }
    
    /**
     * 生成鉴权URL
     * 根据讯飞开放平台WebSocket API的鉴权要求实现
     * 
     * @param hostUrl 服务地址
     * @param apiKey API Key
     * @param apiSecret API Secret
     * @return 带鉴权参数的URL
     * @throws Exception 鉴权异常
     */
    private String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
        // 将wss协议转换为https，因为java.net.URL不支持wss协议
        String httpUrl = hostUrl.replace("wss://", "https://");
        URL url = new URL(httpUrl);
        
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        
        // 1. 拼接signature原始字段(signature_origin)
        // 格式为: host: $host\ndate: $date\n$request-line
        StringBuilder builder = new StringBuilder("host: ")
                .append(url.getHost())
                .append("\n")
                .append("date: ")
                .append(date)
                .append("\n")
                .append("GET ")
                .append(url.getPath())
                .append(" HTTP/1.1");
        
        log.debug("signature原始字段: {}", builder.toString());
        
        // 2. 使用hmac-sha256算法结合apiSecret对signature_origin签名
        Charset charset = StandardCharsets.UTF_8;
        Mac mac;
        try {
            // 尝试使用讯飞推荐的算法名称
            mac = Mac.getInstance("hmacsha256");
        } catch (NoSuchAlgorithmException e) {
            // 如果不支持，则使用标准名称
            mac = Mac.getInstance("HmacSHA256");
        }
        SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), mac.getAlgorithm());
        mac.init(spec);
        byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));
        
        // 3. 对签名结果进行base64编码获得signature
        String signature = Base64.getEncoder().encodeToString(hexDigits);
        log.debug("signature: {}", signature);
        
        // 4. 构建authorization字段，格式为:
        // api_key="$api_key", algorithm="hmac-sha256", headers="host date request-line", signature="$signature"
        String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"",
                apiKey, "hmac-sha256", "host date request-line", signature);
        log.debug("authorization (base64编码前): {}", authorization);
        
        // 5. 对authorization进行base64编码
        String base64Auth = Base64.getEncoder().encodeToString(authorization.getBytes(charset));
        log.debug("authorization (base64编码后): {}", base64Auth);
        
        // 6. 构建最终的鉴权URL
        HttpUrl resultUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath())
                .newBuilder()
                .addQueryParameter("authorization", base64Auth)
                .addQueryParameter("date", date)
                .addQueryParameter("host", url.getHost())
                .build();
        
        // 转换回wss协议
        String authUrl = resultUrl.toString().replace("https://", "wss://");
        log.info("生成的鉴权URL: {}", authUrl);
        return authUrl;
    }
} 